package types

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"hash/crc32"
	"io"

	"gitee.com/thubcc/p2pgit/constant"
	"github.com/a4a881d4/gitcrawling/packext"
)

// Header32 the addtion header of git object
type Header32 struct {
	Hash   Hash   // object hash
	Size   uint32 // size of object in
	BCRC32 uint32 // CRC of compressed object body
	HCRC32 uint32 // CRC of above
}

// Hash256 the raw format of Header32
type Hash256 [32]byte

var (
	// ZeroHash256 nil hash
	ZeroHash256 Hash256
)

// NewHeader32FromOE generate Header32 from object entry
func NewHeader32FromOE(o *packext.ObjEntry) (r *Header32) {
	if o == nil {
		return nil
	}
	r = &Header32{}
	r.Hash = Hash(o.Hash)
	r.Size = o.Size
	r.BCRC32 = o.CRC32
	r.HCRC32 = r.CRC()
	return
}

// NewHeader32FromFile read Header32 from pack file
func NewHeader32FromFile(r io.Reader) (h32 *Header32, err error) {
	var hash Hash256
	_, err = io.ReadFull(r, hash[:])
	if err != nil {
		return
	}
	return hash.ToHeader32()
}

// NewHeader32FromString read Header32 from string
func NewHeader32FromString(s string) (h32 *Header32, err error) {
	var buf []byte
	buf, err = hex.DecodeString(s)
	if err != nil {
		return
	}
	var hash Hash256
	copy(hash[:], buf)

	return hash.ToHeader32()
}

// ToHash256 convert Header32 to raw format
func (h *Header32) ToHash256() (buf Hash256) {
	h.HCRC32 = h.CRC()
	copy(buf[:20], h.Hash[:])
	binary.BigEndian.PutUint32(buf[20:24], h.Size)
	binary.BigEndian.PutUint32(buf[24:28], h.BCRC32)
	binary.BigEndian.PutUint32(buf[28:], h.HCRC32)
	return
}

// CRC caculate CRC of Header32
func (h *Header32) CRC() (crc uint32) {
	c := crc32.NewIEEE()
	c.Reset()
	c.Write(h.Hash[:])
	var buf [4]byte
	binary.BigEndian.PutUint32(buf[:], h.Size)
	c.Write(buf[:])
	binary.BigEndian.PutUint32(buf[:], h.BCRC32)
	c.Write(buf[:])
	crc = c.Sum32()
	return
}

// Check check Header32 crc
func (h *Header32) Check() bool {
	return h.CRC() == h.HCRC32
}

// ToHeader32 convert raw to Header32
func (buf *Hash256) ToHeader32() (h *Header32, err error) {
	h = &Header32{}
	copy(h.Hash[:], buf[:20])
	h.Size = binary.BigEndian.Uint32(buf[20:24])
	h.BCRC32 = binary.BigEndian.Uint32(buf[24:28])
	h.HCRC32 = binary.BigEndian.Uint32(buf[28:])
	if !h.Check() {
		err = constant.ErrorBadCrc
	}
	return
}

func (h *Header32) String() string {
	return fmt.Sprintf("%s S:%10d CRC:%8x HCRC:%8x",
		h.Hash.String(),
		h.Size,
		h.BCRC32,
		h.HCRC32,
	)
}
